using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace StrategyGame
{
    internal class StrategyGame : Microsoft.Xna.Framework.Game
	{
		#region Input

		KeyboardState currentKeyboardState;
        KeyboardState previousKeyboardState;

		/// <summary>
		/// All player input is handled here
		/// </summary>
		void UpdateInput()
		{
			previousKeyboardState = currentKeyboardState;
			currentKeyboardState = Keyboard.GetState();

			// Allows the game to exit
			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || currentKeyboardState.IsKeyDown(Keys.Escape))
			{
				this.Exit();
			}

			//gets a list of every key that is currently pressed
			var pressed_keys = currentKeyboardState.GetPressedKeys();

			foreach (var key in pressed_keys)
			{
				bool just_pressed; //detect pressing and holding. this is only true if the key wasn't being pressed last update, so actions that should only happen once per press can work
				if (previousKeyboardState.IsKeyDown(key))
					just_pressed = false;
				else
					just_pressed = true;

				if (just_pressed)
				{
					//keys that work in any display mode
					switch (key)
					{
						case Keys.Enter:
							UpdateTurn();
							break;

						case Keys.F1:
							CurrentDisplayMode = DisplayMode.Map;
							break;

						case Keys.F2:
							//pressing the button for a different screen when that screen is already open should close it and return to the map
							if (CurrentDisplayMode == DisplayMode.City)
							{
								CurrentDisplayMode = DisplayMode.Map;
							}
							else
							{
								CurrentDisplayMode = DisplayMode.City;

								if (PlayerNation.ActiveCity == null)
									PlayerNation.NextCity();

								CenterCameraOn(PlayerNation.ActiveCity);
							}
							break;

						case Keys.F3:
							{
								CurrentDisplayMode = DisplayMode.TechTree;
							}
							break;
					}

					//displaymode-specific key binds
					switch (CurrentDisplayMode)
					{
						//keybinds for city mode
						case DisplayMode.City:
							CityScreen.KeyPressed(key, just_pressed, this);
							break;

						//keybinds for map mode
						case DisplayMode.Map:
							switch (key)
							{
								case Keys.M:
									if (currentKeyboardState.IsKeyDown(Keys.LeftControl))
										PlayerNation.CHEAT_RevealMap(World);
									else
										PlayerNation.CHEAT_UnRevealMap(World);
									break;
								case Keys.N:
									if (currentKeyboardState.IsKeyDown(Keys.LeftControl))
										PlayerNation.CHEAT_SetFogOfWar(false);
									else
										PlayerNation.CHEAT_SetFogOfWar(true);
									break;

								case Keys.Tab:
									PlayerNation.ActivateNextUnit();
									CenterCameraOn(PlayerNation.ActiveUnit);
									break;

								//unit movement
								case Keys.NumPad1:
									MoveActiveUnit(Unit.MoveDirection.SouthWest);
									break;
								case Keys.NumPad2:
									MoveActiveUnit(Unit.MoveDirection.South);
									break;
								case Keys.NumPad3:
									MoveActiveUnit(Unit.MoveDirection.SouthEast);
									break;
								case Keys.NumPad4:
									MoveActiveUnit(Unit.MoveDirection.West);
									break;
								case Keys.NumPad5:
									MoveActiveUnit(Unit.MoveDirection.Hold);
									break;
								case Keys.NumPad6:
									MoveActiveUnit(Unit.MoveDirection.East);
									break;
								case Keys.NumPad7:
									MoveActiveUnit(Unit.MoveDirection.NorthWest);
									break;
								case Keys.NumPad8:
									MoveActiveUnit(Unit.MoveDirection.North);
									break;
								case Keys.NumPad9:
									MoveActiveUnit(Unit.MoveDirection.NorthEast);
									break;

								//camera movement
								case Keys.Up:
									CameraPosY--;
									break;
								case Keys.Down:
									CameraPosY++;
									break;
								case Keys.Left:
									CameraPosX--;
									break;
								case Keys.Right:
									CameraPosX++;
									break;
							}
							break;
					}
				}
			}
		}

		#endregion

		#region Rendering

		int CameraPosX = 0;
		int CameraPosY = 0;

		GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

		#endregion

		#region Game logic

		TileMap World;

        int CurrentTurn = 0;

        internal Nation PlayerNation { get; private set; }

		internal void CenterCameraOn(IMapObject map_object)
		{
			if (map_object != null)
			{
				CameraPosX = map_object.Position.X;
				CameraPosY = map_object.Position.Y;
			}
			//if this fails we should center on the capital city or something instead
		}

		/// <summary>
		/// move the active unit if one is selected
		/// </summary>
		private bool MoveActiveUnit(Unit.MoveDirection direction)
		{
			Unit activeunit = PlayerNation.ActiveUnit;

			//we need to know these measurements for automatic camera recentering when we move close to the edge of the screen
			int screen_width_in_tiles = GraphicsDevice.ScissorRectangle.Width / World.TileWidth;
			int screen_height_in_tiles = GraphicsDevice.ScissorRectangle.Height / World.TileHeight;
			int camera_offset_x = CameraPosX - (screen_width_in_tiles / 2);
			int camera_offset_y = CameraPosY - (screen_height_in_tiles / 2);

			//how close do we allow units to get to the edge of the screen before the auto-scroll kicks in?
			int edge_scroll_distance = 2;

			if (activeunit != null)
			{
				//return the result of the move command (false if the player tries to move into impassible terrain)
				bool result = activeunit.Move(direction);

				if (result)
				{
					GameSideBar.RegenerateMinimap(graphics.GraphicsDevice, World, PlayerNation);
				}

				int left_edge_in_absolute_coords = camera_offset_x;
				int right_edge_in_absolute_coords = camera_offset_x + screen_width_in_tiles;

				int top_edge_in_absolute_coords = camera_offset_y;
				int bottom_edge_in_absolute_coords = camera_offset_y + screen_width_in_tiles;

				//recenter the camera if necessary
				if (activeunit.Position.X - left_edge_in_absolute_coords <= edge_scroll_distance
					|| right_edge_in_absolute_coords - activeunit.Position.X <= edge_scroll_distance
					|| activeunit.Position.Y - top_edge_in_absolute_coords <= edge_scroll_distance
					|| bottom_edge_in_absolute_coords - activeunit.Position.Y <= edge_scroll_distance)
				{
					CenterCameraOn(PlayerNation.ActiveUnit);
				}

				//if the move command succeeded, and the unit's now out of move points, select the next one
				//todo: only auto-select units which still have move points left
				if (result && activeunit.GetRemainingMovePoints() == 0)
				{
					PlayerNation.ActivateNextUnitWithMovesLeft();

					CenterCameraOn(PlayerNation.ActiveUnit);
				}

				return result;
			}
			else
			{
				//return false if no unit is active
				return false;
			}
		}

		private void UpdateTurn()
		{
			CurrentTurn++;

			World.UpdateTurn(CurrentTurn);

			CenterCameraOn(PlayerNation.ActiveUnit);

			GameSideBar.RegenerateMinimap(graphics.GraphicsDevice, World, PlayerNation);
		}

		#endregion

		#region UI screens & menus

		DisplayMode CurrentDisplayMode = DisplayMode.Map;

		/// <summary>
		/// Represents the possible modes the game can be in (screens, selected on the taskbar)
		/// </summary>
		internal enum DisplayMode
		{
			Map,
			City,
			TechTree
		};

		//sidebar for minimap and stuff
		SideBar GameSideBar;
		TaskBar TopBar;

		CityScreen CityScreen = new CityScreen();
		TechTreeScreen TechTreeScreen = new TechTreeScreen();

		#endregion

		#region XNA methods & Windows events

		void WindowResized(object sender, EventArgs e)
        {
            if (World != null)
                World.ScreenResized(Window.ClientBounds.Width - SideBar.Width, Window.ClientBounds.Height);
        }

        /// <summary>
        /// Loads game content using the XNA content pipeline
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            //these have to be called BEFORE the World is created, so the resources used by the various map objects are all loaded correctly first
            Unit.LoadContent(Content);
            City.LoadContent(Content);
            TileMap.LoadContent(Content);
			Building.LoadContent(Content);
			SideBar.LoadContent(Content);
            TaskBar.LoadContent(Content);
            CityScreen.LoadContent(Content);
			TechTreeScreen.LoadContent(Content);
			SpriteRectangle.LoadContent(graphics.GraphicsDevice);

            GameSideBar = new SideBar(graphics.GraphicsDevice);
            TopBar = new TaskBar();

            World = new TileMap(graphics.GraphicsDevice);

			World.ScreenResized(graphics.GraphicsDevice.ScissorRectangle.Width - SideBar.Width, graphics.GraphicsDevice.ScissorRectangle.Height);

            //create a new nation (at PlayerNation) and add it to the world
            PlayerNation = new Nation(Nation.NationID.Sumer);
            World.AddNation(PlayerNation);

			Point[] starting_locations = World.GenerateStartingPositions(4);
			Nation[] npc_nations = new Nation[3];

			npc_nations[0] = new Nation(Nation.NationID.Britain);
			npc_nations[1] = new Nation(Nation.NationID.Greece);
			npc_nations[2] = new Nation(Nation.NationID.Japan);

			for (int i = 0; i < 3; ++i)
			{
				World.AddNation(npc_nations[i]);

				string city = npc_nations[i].NextCityName();

				npc_nations[i].AddCity(city, starting_locations[i].X, starting_locations[i].Y, World);
			}

			Point player_start = starting_locations[3];

            //give the nation a city
            PlayerNation.AddCity(PlayerNation.NextCityName(), player_start.X, player_start.Y, World);

            UpdateTurn();

            PlayerNation.NextCity();

            CenterCameraOn(PlayerNation.ActiveCity);
        }

        /// <summary>
        /// This game's override of the XNA Game class Update method
        /// </summary>
        /// <param name="gameTime">Game timing object</param>
        protected override void Update(GameTime gameTime)
        {
			UpdateInput();

            base.Update(gameTime);
        }

        /// <summary>
        /// This game's override of the XNA Game class Draw function
        /// </summary>
        /// <param name="gameTime">Game timing object</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            SpriteBatch sprites = new SpriteBatch(GraphicsDevice);
            sprites.Begin();

            Rectangle content_area = new Rectangle();
            content_area.Width = GraphicsDevice.Viewport.Width - SideBar.Width;
            content_area.Height = GraphicsDevice.Viewport.Height - TaskBar.Height;
            content_area.X = 0;
            content_area.Y = 0;

            switch (CurrentDisplayMode)
            {
                case DisplayMode.City:
                    //camera is always centered on the city in city mode
                    //todo: implement this

                    World.Draw(sprites, PlayerNation, CameraPosX, CameraPosY); //this is still drawn, the city screen works as an overlay on the map
                    CityScreen.Draw(sprites, content_area, PlayerNation);
                    break;

				case DisplayMode.TechTree:
					TechTreeScreen.Draw(sprites, content_area, PlayerNation);
					break;
				
				default:
                    World.Draw(sprites, PlayerNation, CameraPosX, CameraPosY);
                    break;
            }   
			
            GameSideBar.Draw(sprites, GraphicsDevice, CurrentTurn, World, PlayerNation, new Point(CameraPosX, CameraPosY));
            TopBar.Draw(sprites, GraphicsDevice, PlayerNation, CurrentDisplayMode);

            sprites.End();

            base.Draw(gameTime);
        }

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor for the Game class
		/// </summary>
		public StrategyGame()
		{
			graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";

			Window.AllowUserResizing = true;
			Window.ClientSizeChanged += new EventHandler<EventArgs>(WindowResized);
		}

		#endregion
	}
}
